Skip to content

UI pass: typography, theme toggle, backlink cards, TOC hierarchy, self-hosted fonts, MCP shell#636

Merged
srid merged 11 commits intomasterfrom
exact-coming
Apr 21, 2026
Merged

UI pass: typography, theme toggle, backlink cards, TOC hierarchy, self-hosted fonts, MCP shell#636
srid merged 11 commits intomasterfrom
exact-coming

Conversation

@srid
Copy link
Copy Markdown
Owner

@srid srid commented Apr 21, 2026

Summary

A top-to-bottom visual refresh of the default Emanote theme, arrived at through iterative feedback against the live dev server. The goal was to land somewhere colourful and fun but not jarring — evocative of neuron.zettel.page's character, suitable for Zettelkasten notes as much as tech docs or personal pages — without making the theme feel like a template everyone else already uses.

What changed

Typography. Swapped the old Source Serif stack for Lora (prose), Space Grotesk (UI chrome + headings), and Space Mono (code). All three are self-hosted under _emanote-static/fonts/ as woff2 + a generated fonts.css with relative URLs, so generated static sites no longer phone home to fonts.gstatic.com.

Theme toggle. A persistent dark/light mode toggle lives in the sidebar (and, critically, in the no-sidebar "neuron-style" layout too, via the top-right button row). State is kept in localStorage; an early-load script in base.tpl applies the .dark class before first paint to avoid a flash of wrong theme. Everything previously keyed off prefers-color-schemeskylighting.css, the stork search dialog — was rewired to the manual toggle via .dark class selectors (and a tiny MutationObserver in the stork wrapper for live swap).

Note title is back to the Emanote brand treatment: bg-primary-600, white, centered, rounded-2xl. That look is the Emanote brand; an earlier iteration stripped it and it immediately felt generic.

Backlinks. Re-done as a 2-column card grid with a thin primary left-accent (border-l-2) and subtle shadow, no outer panel bg/border. The "Links to this page" block now picks up the sans font via #backlinks being added to the UI-chrome selector.

Links. Wikilinks get a subtle primary-50/primary-950 hover background (no underline); external links switch to hover-only underline. The wikilink background uses -mx-1 px-1 rounded so text doesn't reflow on hover.

TOC. Replaced the old window.onscroll scroll-spy with an IntersectionObserver (#520's long-standing TODO). On top of that: depth-based visual hierarchy — each nested <ul> level in the "On this page" list steps down font-size and colour, so a 30-heading page stays scannable at a glance. Active-item styling now uses var(--color-primary-*) so per-site theme overrides flow through cleanly.

Sidebar. Removed the tag-index and expand-tree shortcut icons — search and theme toggle only. Folgezettel tree depth rails (#sidebar .pl-2 .pl-2 { border-left: ... }) keep the nested structure legible.

Smaller polishes. Callouts use color-mix(in srgb, ${color} 8%, transparent) for tint backgrounds (no more naive ${color}10). External-link glyphs are drawn with mask-image + currentColor so they inherit link colour in both themes. kbd restyled with theme variables. Task-list items use :has(> svg.--ema-checkbox) to swap the disc bullet for a flex-aligned checkbox. Footnotes re-done as a semantic <aside> + <ol> instead of the old scale-x-90 squish. Nested-list vertical rhythm fixed. Mermaid diagrams re-render on theme toggle.

Infra/MCP. Added {{nix_shell}} prefixing in justfile so just run works outside a Nix shell, plus a thin just mcp-chrome-devtools recipe invoked from .mcp.json. A new nix/chrome-devtools/shell.nix bundles Chromium + pins chrome-devtools-mcp@0.21.0 (adapted from juspay/kolu#650) so the chrome-devtools MCP server has a headless browser to drive.

Docs. docs/guide/html-template/fonts.md updated to reflect the new default stack.

Notable tradeoffs

  • Self-hosted fonts add ~612 KB to the static layer (29 woff2 blobs across Lora + Space Grotesk + Space Mono subsets). The offline-capable generated site is worth the bytes; browsers cache the blobs per-origin anyway.
  • Dropping the tag/expand-tree shortcuts from the sidebar is a deliberate simplification — the pages are still reachable via breadcrumbs and the full URL.

Test plan

  • Verify both layouts (with-sidebar and neuron-style no-sidebar) render correctly in light + dark mode.
  • Exercise the theme toggle from the sidebar and from the neuron-layout top-right button; confirm persistence across reloads.
  • Open the stork search dialog in both themes; confirm input, results list, and highlight contrast are correct.
  • View a code block in both themes; confirm syntax highlighting follows the manual toggle (not just OS preference).
  • Browse a long-outlined page (e.g. /yaml-config) and confirm the TOC shows depth-based hierarchy and the active-item tracker follows scroll.
  • Confirm cabal build all and cabal test all succeed.
  • Confirm generated static site produces no fonts.googleapis.com network requests.

srid added 8 commits April 21, 2026 09:32
Typography: Fraunces (variable serif) + JetBrains Mono replace the
system stack. Per-context opsz/SOFT/WONK variation tuning gives
display headings vs. prose vs. sidebar distinct voices from one
font file.

Theme colour: the full-bleed primary banner on note-title is
replaced with a left-accent bar + primary-coloured heading text,
so the themed colour is expressive without shouting. Wikilinks
drop the pill background in favour of bold primary text with a
hover underline.

Backlinks: rendered as a responsive card grid, each with a themed
left-border accent and the context excerpt inline.

Folgezettel tree rails: nested sidebar levels get a subtle
left-border rail so depth is visible at a glance.

TODOs addressed:
- TOC scroll-spy: window.onscroll loop → IntersectionObserver
  (issue #520)
- Task-lists: `li:has(> svg.--ema-checkbox)` + flex for proper
  alignment, no stray disc bullets
- Callout background: naive "${color}10" hex-suffix → color-mix()
- External-link glyph: hardcoded gray SVG → mask-image +
  currentColor, one rule for light and dark
- kbd: heavy 3D shadows → flat border with theme-variable colours
- Sidebar sizing: inline <style> workaround replaced with
  Tailwind v4 md:min-w-52/xl:min-w-72
- Mobile breadcrumb: cramped icon row → overflow-x-auto trail

Footnotes: scale-x-90 squish + ad-hoc header/indent → semantic
<aside> + <ol> with mono markers and proper spacing. Nested list
margins tightened so a bulleted sub-list inside an ordered item
doesn't push siblings apart.

Chrome DevTools MCP: .mcp.json now dispatches to a just recipe
that runs chrome-devtools-mcp from a standalone shell.nix at
nix/chrome-devtools/shell.nix. Chromium is bundled (Emanote is
the consumer, following the boundary from juspay/kolu#650), so
the MCP works on machines without a system Chrome.

justfile: {{nix_shell}} auto-detect prefix lets `just run`/`just
test`/etc. work both inside and outside the Nix devshell.
Swap hardcoded #3b82f6/#eff6ff/#1d4ed8 (blue) for
var(--color-primary-*). On pages that set template.theme: green
(or any other palette), the active TOC marker now tracks the
site colourway instead of staying blue.
Three voices now:
- Fraunces (serif, variable) — display headings and prose body
- Inter (sans) — sidebar, breadcrumbs, TOC, metadata chrome
- JetBrains Mono — code

Having the chrome in a dedicated UI sans at small sizes reads
tighter than Fraunces-everywhere (kolu.dev's approach) while
keeping the distinctive Fraunces personality where it matters.
Class-based dark variant:
- @custom-variant dark (&:where(.dark, .dark *)) in both the
  Tailwind CLI input (Tailwind.hs) and the browser CDN
  config (tailwindBrowserConfig), so dark:* classes activate
  from an html.dark class.

Early-load script in base.tpl reads localStorage before first
paint (with OS preference as fallback) and sets the class plus
color-scheme, avoiding a light→dark flash on reload.

Toggle button in the sidebar header flips the html.dark class,
persists the choice, and reloads when a mermaid diagram is on
the page (mermaid reads the scheme at init only).

Fixed stork-search-head.tpl: it was initializing
`window.emanote = {}` destructively, clobbering the theme
helper. Changed to `window.emanote = window.emanote || {}`
so later modules can co-exist.

Body prose font-variation-settings tightened to 'opsz' 14,
'SOFT' 60 — the same values kolu.dev uses for their body, which
gives Fraunces its warm humanist feel at reading sizes.
- skylighting.css: switch prefers-color-scheme media queries to a
  .dark class selector so syntax highlighting follows the manual toggle.
- backlinks.tpl: drop the heavy top/bottom/right borders; keep the
  primary-colour left accent + subtle shadow.
- stork-search: load both light and dark stylesheets unconditionally
  and pick the wrapper class from the .dark class on <html> (via a
  MutationObserver) instead of matchMedia, so the search dialog
  tracks the user's manual theme choice.
The backlink card already has a thick primary left accent; the thin
primary-200 rail on the inner context div doubled up with it.
- Fonts: swap Google Fonts CDN for self-hosted bundle under
  _emanote-static/fonts/ (woff2 + generated fonts.css with relative
  URLs). Static sites no longer reach out to fonts.gstatic.com.
  Typography: Lora (prose), Space Grotesk (UI chrome + headings),
  Space Mono (code).
- Note title: restore the Emanote brand treatment
  (bg-primary-600/primary-800, white centered, rounded-2xl).
- Backlinks: drop outer panel bg/border/shadow; thinner
  border-l-2 primary-400 per card; make the block pick up the sans
  font by scoping #backlinks into the UI-chrome selector.
- Wikilinks: subtle primary-50/primary-950 hover background
  (no underline); external links get hover-only underline.
- Context: remove redundant inner left border rail.
- Sidebar: drop tag-index + expand-tree shortcut icons — search
  and theme toggle only.
- No-sidebar layout: mirror the sidebar's theme toggle in the
  top-right button row so neuron-style pages can flip theme too.
- TOC: visual hierarchy by depth — each nested UL level steps
  down font-size and colour so long outlines stay scannable;
  active-item rule re-scoped under #toc so it still wins.
Add _emanote-static/fonts/*.{woff2,css} to data-files so the Nix
build packages the bundled Lora/Space Grotesk/Space Mono fonts
into the static layer. Without this, generated sites 404 on
fonts.css and the link checker fails.
Comment thread nix/chrome-devtools/shell.nix Outdated
srid added 3 commits April 21, 2026 10:40
…ate.sh

- docs/guide/html-template/fonts.md: swap the Fraunces + JetBrains
  Mono blurb for the new self-hosted Lora + Space Grotesk + Space
  Mono stack; mention the --font-serif/sans/mono CSS-variable
  override path.
- docs/guide/html-template/dark-mode.md: drop the stale 'automatic
  via prefers-color-scheme + browser extension to toggle' framing;
  document the manual toggle, localStorage persistence, and the
  .dark class conventions.
- CHANGELOG: expand the 1.6.0.0 Unreleased UI revamp entry with
  typography, theme toggle, backlinks, TOC, wikilinks, and sidebar
  simplifications from this PR.
- fonts/update.sh + justfile fonts-update: reproducible re-download
  of the self-hosted Google Fonts bundle. Script is idempotent
  (skips already-present blobs) and explains the hash-based
  filenames that Google's CDN hands back.
- nix/chrome-devtools/shell.nix: pull nixpkgs straight from the
  top-level flake.lock (builtins.fetchTree with owner/repo/rev/
  narHash) instead of tracking nixos-unstable independently. One
  pin to maintain.
- justfile: drop the fonts-update recipe; invoke the script
  directly when needed.
- CHANGELOG: trim the 1.6.0.0 UI-revamp entry to three lines.
@srid srid merged commit d4a9e13 into master Apr 21, 2026
4 checks passed
@srid srid deleted the exact-coming branch April 21, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant